#include <jni.h>
#include <string>
#include <errno.h>
#include <android/log.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <linux/input.h>
#include <sys/epoll.h>

#define TAG "KeyEvent" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型

#define BITS_PER_LONG   (sizeof(long)*8)
#define BIT_WORD(nr)    ((nr) / BITS_PER_LONG)

//now, only support one key that passed when initialing
static int scan_code = -1;
static int scan_state = -1;
static int fdInput = -1;
//epoll文件指针
static int epoll_fd = -1;

static JavaVM *gs_jvm = NULL;
static jobject gs_object = NULL;

//=================扫描事件的方法==================//

//register epoll events for fd
static void epoll_register(int epoll_fd, int fd) {
    struct epoll_event ev;
    int ret;

    ev.events = EPOLLIN; //interested in receiving data
    ev.data.fd = fd;
    do {
        //register events for fd
        ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);
    } while (ret < 0 && errno == EINTR);
}

//remove epoll events for fd
static void epoll_unregister(int epoll_fd, int fd) {
    int ret;
    do {
        ret = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
    } while (ret < 0 && errno == EINTR);
}

static int test_bit(int nr, const volatile unsigned long *addr) {
    return static_cast<int>(1UL & (addr[BIT_WORD(nr)] >> (nr & (BITS_PER_LONG - 1))));
}

void jniKeyDown(int keyCode) {
    if (gs_jvm != NULL && gs_object != NULL) {
        LOGD("jniKeyDown:%d", keyCode);
        JNIEnv *env;
        gs_jvm->AttachCurrentThread(&env, NULL);
        static jmethodID mid = NULL;
        if (mid == NULL) {
            jclass clazz = env->GetObjectClass(gs_object);
            mid = env->GetMethodID(clazz, "onKeyDown", "(I)V");
            env->DeleteLocalRef(clazz);
        }
        env->CallVoidMethod(gs_object, mid, keyCode);
    }
}

static void *scanning(void *args) {
    int n = 0;
    int i = 0;
    int res;
    unsigned long key_b[KEY_MAX / BITS_PER_LONG + 1];
    struct input_event event;
    //epoll_events
    struct epoll_event events[1];

    LOGD("key scanning...");
    //create epoll
    epoll_fd = epoll_create(1);
    if (epoll_fd == -1) {
        LOGE("epoll_create failed!\n");
        goto exit;
    }
    //create & register epoll
    epoll_register(epoll_fd, fdInput);

    while (scan_code >= 0) {
        if (n == 0) {
            //获取按键状态
            memset(key_b, 0, sizeof(key_b));
            n = ioctl(fdInput, EVIOCGKEY(sizeof(key_b)), key_b);
            if (n > 0) {
                scan_state = test_bit(scan_code, key_b);
                LOGI("Query key state:%d\n", scan_state);
            } else {
                LOGE("Query key fail:%d, %s\n", n, strerror(errno));
            }
        }
        n = epoll_wait(epoll_fd, events, 1, 5000); //检测是否有按键被按下
        if (n < 0) {
            LOGE("epoll_wait error:%s", strerror(errno));
            continue;
        }
        for (i = 0; i < n; i++) {
            if (events[i].data.fd == fdInput) { //是否有读事件
                res = static_cast<int>(read(fdInput, &event, sizeof(event)));
                if (res < (int) sizeof(event)) {
                    LOGE("could not get event\n");
                    continue;
                }
                if (event.type == EV_KEY) {
                    LOGI("Got key: %04x %08x\n", event.code, event.value);
                    scan_state = event.value;
                    if (event.value == 1) {
                        jniKeyDown(event.code);
                    }
                }
            }
        }
    }

    if (gs_jvm != NULL) {
        gs_jvm->DetachCurrentThread();
    }


    exit:
    if (epoll_fd != -1) {
        epoll_unregister(epoll_fd, fdInput);
        close(epoll_fd);
        epoll_fd = -1;
    }
    if (fdInput != -1) {
        close(fdInput);
        fdInput = -1;
    }
    return 0;
}

//=================================================//

//set non-blocking for fd
static int set_non_blocking(int fd) {
    int opts;

    opts = fcntl(fd, F_GETFL);
    if (opts < 0) {
        LOGE("fcntl F_GETFL error: %s", strerror(errno));
        return -1;
    }
    opts = (opts | O_NONBLOCK);
    if (fcntl(fd, F_SETFL, opts) < 0) {
        LOGE("fcntl F_SETFL error: %s", strerror(errno));
        return -1;
    }
    return 0;
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_xuexiang_keycodelistener_KeyEventManager_startKeyScan(JNIEnv *env, jobject instance,
                                                               jstring path_, jint code) {
    pthread_t thread;
    const char *path = env->GetStringUTFChars(path_, NULL);

    LOGI("startKeyScan...");
    scan_code = code;
    if (code < 0 || path == NULL || strlen(path) == 0) {
        errno = EINVAL;
        LOGE("Invalid path path\n");
        env->ReleaseStringUTFChars(path_, path);
        goto fail;
    }
    LOGI("scanning key: %d of %s\n", scan_code, path);

    //open input pathice
    fdInput = open(path, O_RDONLY | O_CLOEXEC);
    if (fdInput < 0) {
        LOGE("open pathice: %s failed: %s\n", path, strerror(errno));
        env->ReleaseStringUTFChars(path_, path);
        goto fail;
    }
    env->ReleaseStringUTFChars(path_, path);
    //set non-blocking
    set_non_blocking(fdInput);

    if (gs_jvm == NULL) {
        env->GetJavaVM(&gs_jvm); //获取jvm
    }

    if (gs_object == NULL) {
        gs_object = env->NewGlobalRef(instance); //获取obj
    }

    //create scanning thread
    if (pthread_create(&thread, NULL, scanning, (void *) NULL) != 0) {
        LOGE("create key scanning thread failed!\n");
        goto fail;
    }

    return 0;

    fail:
    scan_code = -1;
    if (fdInput != -1) {
        close(fdInput);
        fdInput = -1;
    }
    return -1;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_xuexiang_keycodelistener_KeyEventManager_stopKeyScan(JNIEnv *env, jobject instance) {
    scan_code = -1;
    if (gs_object != NULL) {
        env->DeleteGlobalRef(gs_object);
        gs_object = NULL;
    }
}